import * as _ from 'lodash';
import * as fs from 'fs-extra';

import path from 'path';
import walkSync from 'walk-sync';

import { Driver } from './driver';
import { DriverPath } from './driverPath';
import { DriverWatcher } from './driverWatcher';
import { Drivers, DriverConfig } from './type';
import { DRIVER_CONFIG_FILE_NAME } from './constant';
import { logger, JsonFileUtility, filterNotExistParameters, AtLeastOne } from '$common';
export class DriverManager {

  private static _instance: DriverManager;

  public static get Instance() {
    if (!this._instance) {
      this._instance = new DriverManager(DriverPath.driverAssemblyPath);
    }
    return this._instance;
  }

  private _drivers: Drivers;

  private constructor(driverFolder: string) {
    this._drivers = this._loadDrivers(driverFolder);
    this._initDriverWatcher();
  }

  private _initDriverWatcher() {
    DriverWatcher.start();
  }

  private _loadDrivers(driverFolder: string): Drivers {

    if (!fs.pathExistsSync(driverFolder)) {
      throw new Error(`could not find ${driverFolder}`);
    }

    logger.debug(`🔍 Recursively find all the files in '${driverFolder}'`);

    const drivers = walkSync(driverFolder, { directories: false })
      .map(m => path.join(driverFolder, m))
      .filter(f => path.basename(f) === DRIVER_CONFIG_FILE_NAME)
      .reduce((drivers, driverConfigPath) => {

        const driverConfig = JsonFileUtility.deserializeFromFile(driverConfigPath, {}) as DriverConfig;

        const propsRequired = ['product', 'canConcurrent', 'executable'];
        const notExistParams = filterNotExistParameters(driverConfig, propsRequired);

        if (!_.isEmpty(notExistParams)) {
          throw new Error(`Driver Config is not properly configured. Missing '${notExistParams.join(', ')}' configuration in ${driverConfigPath}`);
        }
        const { product, canConcurrent, maxConcurrent, executable } = driverConfig;

        drivers[product] = new Driver(product, canConcurrent, maxConcurrent, executable, driverConfigPath);
        return drivers;
      }, {});

    logger.debug(`==> Drivers loaded successfully, ${JSON.stringify(drivers, null, 2)}`);
    return drivers;
  }

  reload(driverAssemblyPath?: string) {
    this._drivers = this._loadDrivers(driverAssemblyPath ? driverAssemblyPath : DriverPath.driverAssemblyPath);
  }

  getDriver(driverName: string): Driver {
    return this._drivers[driverName];
  }

  getDrivers(): Drivers {
    return this._drivers;
  }

  updateDriverBySelective(driverName: string, updatedAttributes: AtLeastOne<Omit<Driver, 'driverName'>>) {
    const driver = this.getDriver(driverName);
    if (!_.isEmpty(driver)) {
      const mergedDriver = _.merge(driver, updatedAttributes);
      this._drivers[mergedDriver.driverName] = mergedDriver;
    }
  }
}